package com.tl.satoken.netty;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.tl.satoken.domain.entity.*;
import com.tl.satoken.domain.vo.Heros;
import com.tl.satoken.domain.vo.GameCharacterVo;
import com.tl.satoken.enums.MessageType;
import com.tl.satoken.listener.WebSocketEventListener;
import com.tl.satoken.mapper.HeroMapper;
import com.tl.satoken.mapper.GameCharacterMapper;
import com.tl.satoken.service.RoomService;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

import static com.tl.satoken.enums.MessageType.coinStart;

@Slf4j
@Component
@ChannelHandler.Sharable
public class WebSocketFrameHandler extends SimpleChannelInboundHandler<Object> {

    // todo 维护 广播的关键性代码
    private static final ConcurrentHashMap<String, ChannelHandlerContext> clients = new ConcurrentHashMap<>();  // clientId 与 ChannelHandlerContext 的映射
    @Autowired
    private HeroMapper heroMapper;
    @Autowired
    private GameCharacterMapper gameCharacterMapper;
    @Autowired
    private  RoomService roomService;
    @Autowired
    private  WebSocketEventListener webSocketEventListener;
    private static ObjectMapper objectMapper = new ObjectMapper();
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) {
        if (msg instanceof TextWebSocketFrame) {
            String text = ((TextWebSocketFrame) msg).text();
            String clientId = ctx.channel().id().asShortText();
            log.info("收到客户端[{}]消息: {}", clientId, text);
            
            try {
                GameMessage gameMessage = objectMapper.readValue(text, GameMessage.class);
                handleGameMessage(ctx, clientId, gameMessage);
            } catch (Exception e) {
//                log.error("解析游戏消息失败", e);
//                sendError(ctx, "消息格式错误");
            }
        } else if (msg instanceof BinaryWebSocketFrame) {
            log.info("收到二进制消息");
        } else if (msg instanceof CloseWebSocketFrame) {
            log.info("客户端请求关闭连接");
            ctx.channel().close();
        } else if (msg instanceof PingWebSocketFrame) {
            log.info("收到Ping消息");
            ctx.channel().writeAndFlush(new PongWebSocketFrame());
        }
    }
    
    private void handleGameMessage(ChannelHandlerContext ctx, String clientId, GameMessage message) {
        if (roomService == null || webSocketEventListener == null) {
            return;
        }
        try {
            switch (message.getType()) {
                case enter:
                    handleJoinGame(ctx, clientId, message);
                    break;
                case leaveGame:
                    handleLeaveGame(ctx, clientId, message);
                    break;
                case updatePlayer:
                    handleUpdatePlayer(ctx, clientId, message);
                    break;
                case emojiSent:
                    handleEmojiSent(ctx, clientId, message);
                    break;
                case getHeroes:
                    handleGetHeros(ctx, clientId, message);
                    break;
                case motion:
                    handleMotion(ctx, clientId, message);
                    break;
                case emotion:
                    handleEmotion(ctx, clientId, message);
                    break;
                case coinGrab:
                    handleCoinGrab(ctx, clientId, message);
                    break;
                default:
                    log.warn("未知消息类型: {}", message.getType());
            }
        } catch (Exception e) {
            log.error("处理游戏消息失败", e);
            sendError(ctx, "处理消息失败: " + e.getMessage());
        }
    }

    private void handleCoinGrab(ChannelHandlerContext ctx, String clientId, GameMessage message) {
        try {
            // 从Redis中获取玩家会话信息
            String sessionKey = "game:session:" + clientId;
            RedisPlayer redisPlayer = (RedisPlayer) redisTemplate.opsForValue().get(sessionKey);
            Player sessionPlayer = redisPlayer != null ? redisPlayer.toPlayer() : null;
            
            if (sessionPlayer == null) {
                log.warn("抢金币的玩家会话不存在: {}", clientId);
                sendCoinGrabResult(ctx, null, false);
                return;
            }
            
            String roomId = sessionPlayer.getRoomId();
            if (roomId == null) {
                log.warn("玩家 heroId: {} 不在任何房间中", sessionPlayer.getHeroId());
                sendCoinGrabResult(ctx, null, false);
                return;
            }
            
            // 获取金币ID
            String coinId = message.getId();
            if (coinId == null || coinId.trim().isEmpty()) {
                log.warn("玩家 heroId: {} 抢金币时金币ID为空", sessionPlayer.getHeroId());
                sendCoinGrabResult(ctx, null, false);
                return;
            }
            
            log.info("英雄 heroId: {} 尝试抢金币: {}",  sessionPlayer.getHeroId(), coinId);
            
            // 使用分布式锁防止并发抢金币
            String lockKey = "coin:lock:" + coinId;
            String lockValue = clientId + ":" + System.currentTimeMillis();
            
            // 尝试获取锁，设置5秒过期时间
            Boolean lockAcquired = redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, 5, TimeUnit.SECONDS);
            
            if (lockAcquired != null && lockAcquired) {
                try {
                    // 获取锁成功，检查金币是否存在且未被抢走
                    String coinKey = "coin:" + coinId;
                    Map<Object, Object> coinData = redisTemplate.opsForHash().entries(coinKey);
                    
                    if (coinData.isEmpty()) {
                        log.warn("金币 {} 不存在", coinId);
                        sendCoinGrabResult(ctx, coinId, false);
                        return;
                    }
                    
                    String currentHeroId = (String) coinData.get("heroId");
                    if (currentHeroId != null && !currentHeroId.trim().isEmpty()) {
                        log.warn("金币 {} 已被玩家 {} 抢走", coinId, currentHeroId);
                        sendCoinGrabResult(ctx, coinId, false);
                        return;
                    }
                    
                    // 抢金币成功，更新金币的heroId字段
                    redisTemplate.opsForHash().put(coinKey, "heroId", String.valueOf(sessionPlayer.getHeroId()));
                    
                    log.info("玩家 heroId: {} 成功抢到金币: {}", sessionPlayer.getHeroId(), coinId);
                    sendCoinGrabResult(ctx, coinId, true);
                    
                    // 广播抢金币消息给同房间其他玩家
                    broadcastCoinGrabMessage(roomId, coinId, sessionPlayer.getHeroId(), sessionPlayer.getHeroId());
                    
                } finally {
                    // 释放锁
                    String currentLockValue = (String) redisTemplate.opsForValue().get(lockKey);
                    if (lockValue.equals(currentLockValue)) {
                        redisTemplate.delete(lockKey);
                    }
                }
            } else {
                // 获取锁失败，说明有其他玩家正在抢这个金币
                log.warn("玩家 heroId: {} 抢金币 {} 失败，锁被其他玩家占用", sessionPlayer.getHeroId(), coinId);
                sendCoinGrabResult(ctx, coinId, false);
            }
            
        } catch (Exception e) {
            log.error("处理抢金币消息失败", e);
            sendCoinGrabResult(ctx, null, false);
        }
    }

    private void handleEmotion(ChannelHandlerContext ctx, String clientId, GameMessage message) {
        try {
            // 从Redis中获取玩家会话信息
            String sessionKey = "game:session:" + clientId;
            RedisPlayer redisPlayer = (RedisPlayer) redisTemplate.opsForValue().get(sessionKey);
            Player sessionPlayer = redisPlayer != null ? redisPlayer.toPlayer() : null;
            
            if (sessionPlayer == null) {
                log.warn("发送表情包的玩家会话不存在: {}", clientId);
                return;
            }
            
            String roomId = sessionPlayer.getRoomId();
            if (roomId == null) {
                log.warn("玩家 {} 不在任何房间中", sessionPlayer.getUsername());
                return;
            }
            
            // 获取表情包图片
            String image = message.getImage();
            if (image == null || image.trim().isEmpty()) {
                log.warn("玩家 {} 发送的表情包图片为空", sessionPlayer.getUsername());
                return;
            }
            
            log.info("玩家 {} 发送表情包，房间: {}, 图片: {}", sessionPlayer.getUsername(), roomId, image);
            
            // 广播表情包消息给房间内其他玩家，排除发送者自己
            broadcastEmotionMessage(roomId, sessionPlayer.getId(), sessionPlayer.getHeroId(), image);
            
        } catch (Exception e) {
            log.error("处理表情包消息失败", e);
        }
    }

    private void handleMotion(ChannelHandlerContext ctx, String clientId, GameMessage message) {
        try {
            // 从Redis中获取玩家会话信息
            String sessionKey = "game:session:" + clientId;
            RedisPlayer redisPlayer = (RedisPlayer) redisTemplate.opsForValue().get(sessionKey);
            Player sessionPlayer = redisPlayer != null ? redisPlayer.toPlayer() : null;
            
            if (sessionPlayer == null) {
                log.warn("获取玩家信息失败，会话不存在: {}", clientId);
                return;
            }
            
            String roomId = sessionPlayer.getRoomId();
            if (roomId == null) {
                log.warn("玩家 {} 不在任何房间中", sessionPlayer.getUsername());
                return;
            }
            
            // 更新玩家位置信息
            Position newPosition = new Position(message.getPosition());
            Rotation newRotation = message.getRotation();
            String newAnimation = message.getAnimation();
            
            // 更新Redis中的玩家信息
            redisPlayer.setPosition(newPosition);
            redisPlayer.setRotation(newRotation);
            redisPlayer.setAnimation(newAnimation);
            redisTemplate.opsForValue().set(sessionKey, redisPlayer);
            
            // 更新房间服务中的玩家位置
            // todo 可以先广播再更新redis
            if (roomService.updatePlayerPosition(sessionPlayer.getHeroId(), sessionPlayer)) {
                // 广播移动消息给同房间其他玩家
                broadcastPlayerMotion(roomId, sessionPlayer.getHeroId(), sessionPlayer.getHeroId(), newPosition, newRotation, newAnimation);
                log.info("玩家 {} 移动，位置：({}, {}, {})", sessionPlayer.getHeroId(), newPosition.getX(), newPosition.getY(), newPosition.getZ());
            } else {
                log.warn("玩家 {} 位置更新失败", sessionPlayer.getUsername());
            }
        } catch (Exception e) {
            log.error("处理玩家移动消息失败", e);
        }
    }

    private void handleGetHeros(ChannelHandlerContext ctx, String clientId, GameMessage message) {
        try {
            // 从Redis中获取玩家会话信息
            String sessionKey = "game:session:" + clientId;
            RedisPlayer redisPlayer = (RedisPlayer) redisTemplate.opsForValue().get(sessionKey);
            Player sessionPlayer = redisPlayer != null ? redisPlayer.toPlayer() : null;
            
            if (sessionPlayer == null) {
                log.warn("获取玩家信息失败，会话不存在: {}", clientId);
                sendError(ctx, "玩家会话不存在");
                return;
            }
            
            String roomId = sessionPlayer.getRoomId();
            if (roomId == null) {
                log.warn("玩家 {} 不在任何房间中", sessionPlayer.getUsername());
                sendError(ctx, "玩家不在任何房间中");
                return;
            }
            
            List<Player> roomPlayers = roomService.getPlayersInRoom(roomId);
            log.info("玩家 {} 请求获取房间 {} 中的玩家列表，共 {} 个玩家", 
                sessionPlayer.getUsername(), roomId, roomPlayers.size());
            
            // 将Player对象转换为HeroData对象，排除当前请求的玩家自己
            List<HeroData> heroDataList = roomPlayers.stream()
                    .filter(player -> !player.getHeroId().equals(sessionPlayer.getHeroId())) // 排除自己
                    .map(player -> {
                        HeroData heroData = new HeroData();
                        heroData.setHeroId(player.getHeroId());
                        heroData.setPosition(player.getPosition());
                        heroData.setRotation(player.getRotation());
                        heroData.setAnimation(player.getAnimation());
                        // 获取Hero对象
                        Hero hero = heroMapper.selectById(player.getHeroId());
                        if (hero != null && hero.getCharacterId() != null) {
                            // 根据characterId查找character并封装成GameCharacterVo
                            GameCharacter gameCharacter = gameCharacterMapper.selectById(hero.getCharacterId());
                            if (gameCharacter != null) {
                                GameCharacterVo characterVo = new GameCharacterVo();
                                characterVo.setId(gameCharacter.getId());
                                characterVo.setModelUrl(gameCharacter.getModelUrl());
                                characterVo.setPreviewUrl(gameCharacter.getPreviewUrl());
                                characterVo.setName(gameCharacter.getName());
                                characterVo.setStyleTypeId(gameCharacter.getStyleTypeId());
                                hero.setCharacter(characterVo);
                            }
                        }
                        heroData.setHero(hero);
                        return heroData;
                    })
                    .collect(java.util.stream.Collectors.toList());
            
            // 创建Heros响应对象
            Heros herosResponse = new Heros();
            herosResponse.setType("heros");
            herosResponse.setData(heroDataList);
            
            String messageJson = objectMapper.writeValueAsString(herosResponse);
            ctx.channel().writeAndFlush(new TextWebSocketFrame(messageJson));
            
        } catch (Exception e) {
            log.error("处理获取玩家列表消息失败", e);
            sendError(ctx, "获取玩家列表失败: " + e.getMessage());
        }
    }

    private void handleJoinGame(ChannelHandlerContext ctx, String clientId, GameMessage message) {
        Hero hero = heroMapper.selectById(message.getHeroId());
        try {
            Player player = Player.builder()
                    .id(hero.getUserId())
                    .heroId(message.getHeroId())
                    .sessionId(clientId)
                    .channel(ctx)
                    .animation(message.getAnimation())
                    .position(new Position(message.getPosition()))
                    .rotation(message.getRotation())
                    .build();
            RoomJoinResult roomJoinResult = roomService.joinRoom(player);
            
            if (roomJoinResult.isSuccess()) {
                player.setSessionId(clientId);
                player.setChannel(ctx);
                player.setRoomId(roomJoinResult.getRoomId());

                // 将玩家会话信息存储到Redis中（使用RedisPlayer避免序列化问题）
                String sessionKey = "game:session:" + clientId;
                RedisPlayer redisPlayer = RedisPlayer.fromPlayer(player);
                redisTemplate.opsForValue().set(sessionKey, redisPlayer);
                
                // 发送enterSuccess消息给自己
                sendEnterSuccess(ctx);
                
                // 发送房间金币数据
                sendRoomCoins(ctx, roomJoinResult.getRoomId());
                
                // 向同房间其他玩家发送enter消息
                broadcastPlayerEnter(roomJoinResult.getRoomId(), player);
                
                log.info("玩家 {} 成功加入房间 {}", player.getUsername(), roomJoinResult.getRoomId());
            } else {
                log.warn("玩家 {} 加入房间失败: {}", player.getUsername(), roomJoinResult.getMessage());
                sendError(ctx, roomJoinResult.getMessage());
            }
        } catch (Exception e) {
            log.error("处理加入游戏消息失败", e);
            sendError(ctx, "加入游戏失败: " + e.getMessage());
        }
    }
    
    private void handleLeaveGame(ChannelHandlerContext ctx, String clientId, GameMessage message) {
        try {
            Player player = objectMapper.convertValue(message.getData(), Player.class);
            log.info("玩家主动下线：{}，会话ID: {}", player.getUsername(), clientId);
            webSocketEventListener.removeSessionPlayer(clientId);

            if (roomService.leaveRoom(player.getHeroId())) {
                String roomId = player.getRoomId();
                List<Player> remainingPlayers = roomService.getPlayersInRoom(roomId);
                // 广播给房间内剩余玩家，通知有玩家离开（离开的玩家已断开连接，无需排除）
                broadcastRoomUpdate(roomId, remainingPlayers);
                log.info("房间 {} 剩余玩家数量: {}", roomId, remainingPlayers.size());
            }
        } catch (Exception e) {
            log.error("处理离开游戏消息失败", e);
        }
    }
    
    private void handleUpdatePlayer(ChannelHandlerContext ctx, String clientId, GameMessage message) {
        try {
            Player player = objectMapper.convertValue(message.getData(), Player.class);
            log.info("玩家移动：{}，位置：({}, {})，会话ID: {}",
                player.getUsername(), player.getPosition().getX(), player.getPosition().getY(), clientId);

            // 更新session中的玩家信息（从Redis获取）
            String sessionKey = "game:session:" + clientId;
            RedisPlayer redisPlayer = (RedisPlayer) redisTemplate.opsForValue().get(sessionKey);
            if (redisPlayer != null) {
                redisPlayer.setPosition(player.getPosition());
                // 同步更新其他字段
                redisPlayer.setStatus(player.getStatus());
                redisPlayer.setRoomId(player.getRoomId());
                // 更新Redis中的会话信息
                redisTemplate.opsForValue().set(sessionKey, redisPlayer);
            }
            // updatePlayerPosition 更新redis中玩家的的位置
            if (roomService.updatePlayerPosition(player.getHeroId(), player)) {
                String roomId = player.getRoomId();
                // 只广播移动玩家的位置信息，排除发送者自己
                broadcastPlayerMove(roomId, player);
            } else {
                log.warn("玩家 {} 位置更新失败", player.getUsername());
            }
        } catch (Exception e) {
            log.error("处理玩家移动消息失败", e);
        }
    }
    
    private void handleEmojiSent(ChannelHandlerContext ctx, String clientId, GameMessage message) {
        try {
            // 从Redis中获取玩家会话信息
            String sessionKey = "game:session:" + clientId;
            RedisPlayer redisPlayer = (RedisPlayer) redisTemplate.opsForValue().get(sessionKey);
            Player sessionPlayer = redisPlayer != null ? redisPlayer.toPlayer() : null;
            if (sessionPlayer == null) {
                log.warn("发送表情包的玩家会话不存在: {}", clientId);
                return;
            }
            
            String roomId = sessionPlayer.getRoomId();
            if (roomId == null) {
                log.warn("玩家 {} 不在任何房间中", sessionPlayer.getUsername());
                return;
            }
            
            log.info("玩家 {} 发送表情包，房间: {}", sessionPlayer.getUsername(), roomId);
            
            // 广播表情包消息给房间内其他玩家，排除发送者自己
            broadcastEmojiMessage(roomId, message, sessionPlayer.getId());
            
        } catch (Exception e) {
            log.error("处理表情包消息失败", e);
        }
    }

    private void broadcastRoomUpdate(String roomId, List<Player> players) {
        broadcastRoomUpdate(roomId, players, null);
    }
    
    private void broadcastRoomUpdate(String roomId, List<Player> players, String excludePlayerId) {
        try {
            GameMessage updateMessage = new GameMessage(MessageType.roomUpdate, "SERVER", roomId, players);
            String messageJson = objectMapper.writeValueAsString(updateMessage);
            
            for (Player player : players) {
                // 如果指定了排除的玩家ID，则跳过该玩家
                if (excludePlayerId != null && excludePlayerId.equals(player.getId())) {
                    continue;
                }
                // todo 这个ID是clientId？  答： 就是ID，根据ID获取对应的 player，player 里面有channel对象
                Player sessionPlayer = webSocketEventListener.getPlayerById(player.getId());
                if (sessionPlayer != null && sessionPlayer.getChannel() != null && sessionPlayer.getChannel().channel().isActive()) {
                    sessionPlayer.getChannel().channel().writeAndFlush(new TextWebSocketFrame(messageJson));
                }
            }
        } catch (Exception e) {
            log.error("广播房间更新失败", e);
        }
    }
    
    private void broadcastPlayerMove(String roomId, Player movedPlayer) {
        try {
            // 创建只包含移动玩家位置信息的消息
            GameMessage moveMessage = new GameMessage(MessageType.playerMove, "SERVER", roomId, movedPlayer);
            String messageJson = objectMapper.writeValueAsString(moveMessage);
            
            // 获取房间内所有玩家
            List<Player> roomPlayers = roomService.getPlayersInRoom(roomId);
            int broadcastCount = 0;
            
            for (Player player : roomPlayers) {
                // 排除移动的玩家自己
                if (movedPlayer.getHeroId().equals(player.getHeroId())) {
                    continue;
                }

                Player sessionPlayer = webSocketEventListener.getPlayerById(player.getId());
                if (sessionPlayer != null && sessionPlayer.getChannel() != null && sessionPlayer.getChannel().channel().isActive()) {
                    sessionPlayer.getChannel().channel().writeAndFlush(new TextWebSocketFrame(messageJson));
                    broadcastCount++;
                } else {
                    log.warn("玩家 {} 的session不存在或连接已断开", player.getUsername());
                }
            }
            
            log.debug("向房间 {} 中的 {} 个玩家广播了玩家 {} 的移动消息", roomId, broadcastCount, movedPlayer.getUsername());

        } catch (Exception e) {
            log.error("广播玩家移动消息失败", e);
        }
    }
    
    private void broadcastEmojiMessage(String roomId, GameMessage emojiMessage, Long excludePlayerId) {
        try {
            List<Player> roomPlayers = roomService.getPlayersInRoom(roomId);
            String messageJson = objectMapper.writeValueAsString(emojiMessage);
            
            for (Player player : roomPlayers) {
                // 排除发送者自己
                if (excludePlayerId != null && excludePlayerId.equals(player.getId())) {
                    continue;
                }
                
                Player sessionPlayer = webSocketEventListener.getPlayerById(player.getId());
                if (sessionPlayer != null && sessionPlayer.getChannel() != null && sessionPlayer.getChannel().channel().isActive()) {
                    sessionPlayer.getChannel().channel().writeAndFlush(new TextWebSocketFrame(messageJson));
                }
            }
        } catch (Exception e) {
            log.error("广播表情包消息失败", e);
        }
    }
    
    private void sendError(ChannelHandlerContext ctx, String errorMessage) {
        try {
            GameMessage errorMsg = new GameMessage(MessageType.error, "SERVER", null, errorMessage);
            String messageJson = objectMapper.writeValueAsString(errorMsg);
            ctx.channel().writeAndFlush(new TextWebSocketFrame(messageJson));
        } catch (Exception e) {
            log.error("发送错误消息失败", e);
        }
    }
    
    private void sendCoinGrabResult(ChannelHandlerContext ctx, String coinId, boolean success) {
        try {
            Map<String, Object> resultData = new HashMap<>();
            resultData.put("type", "coinGrabSelf");
            resultData.put("id", coinId);
            resultData.put("success", success);
            
            String messageJson = objectMapper.writeValueAsString(resultData);
            ctx.channel().writeAndFlush(new TextWebSocketFrame(messageJson));
            
            log.debug("发送抢金币结果: coinId={}, success={}", coinId, success);
        } catch (Exception e) {
            log.error("发送抢金币结果失败", e);
        }
    }
    
    private void broadcastCoinGrabMessage(String roomId, String coinId, Integer heroId, Integer excludeHeroId) {
        try {
            Map<String, Object> broadcastData = new HashMap<>();
            broadcastData.put("type", "coinGrab");
            broadcastData.put("id", coinId);
            broadcastData.put("heroId", heroId);
            
            String messageJson = objectMapper.writeValueAsString(broadcastData);
            
            // 获取房间内所有玩家
            List<Player> roomPlayers = roomService.getPlayersInRoom(roomId);
            int broadcastCount = 0;
            
            for (Player player : roomPlayers) {
                // 排除抢到金币的玩家自己
                if (excludeHeroId != null && excludeHeroId.equals(player.getHeroId())) {
                    continue;
                }
                
                // 通过clientId获取对应的channel
                String clientId = player.getSessionId();
                if (clientId != null) {
                    ChannelHandlerContext ctx = clients.get(clientId);
                    if (ctx != null && ctx.channel().isActive()) {
                        ctx.channel().writeAndFlush(new TextWebSocketFrame(messageJson));
                        broadcastCount++;
                    }
                }
            }
            
            log.debug("向房间 {} 中的 {} 个玩家广播了抢金币消息: coinId={}, heroId={}", roomId, broadcastCount, coinId, heroId);
            
        } catch (Exception e) {
            log.error("广播抢金币消息失败", e);
        }
    }

    public ChannelHandlerContext getClientContext(String clientId) {
        return clients.get(clientId);
    }

    // 添加客户端连接
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) {
        String clientId = ctx.channel().id().asShortText();
        clients.put(clientId, ctx);
        log.info("客户端[{}]连接成功，当前在线人数: {}", clientId, clients.size());
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) {
        String clientId = ctx.channel().id().asShortText();
        clients.remove(clientId);
        log.info("客户端[{}]断开连接，当前在线人数: {}", clientId, clients.size());
        
        // 从Redis中获取玩家信息并清理会话
        String sessionKey = "game:session:" + clientId;
        RedisPlayer redisPlayer = (RedisPlayer) redisTemplate.opsForValue().get(sessionKey);
        Player player = redisPlayer != null ? redisPlayer.toPlayer() : null;
        if (player != null && player.getRoomId() != null) {
            // 广播玩家离开消息给同房间其他玩家
            broadcastPlayerLeave(player.getRoomId(), player.getHeroId());
            
            // 从房间中移除玩家
            roomService.leaveRoom(player.getHeroId());
            
            log.info("玩家 {} (heroId: {}) 离开房间 {}", player.getUsername(), player.getHeroId(), player.getRoomId());
        }
        
        // 清理Redis中的会话信息
        redisTemplate.delete(sessionKey);
        
        // 同时清理内存中的会话信息（向后兼容）
        if (webSocketEventListener != null) {
            webSocketEventListener.removeSessionPlayer(clientId);
            
            // 定期清理无效session
            if (clients.size() % 10 == 0) { // 每10个连接清理一次
                webSocketEventListener.cleanupInvalidSessions();
                log.info("清理无效session完成，当前有效session数: {}", webSocketEventListener.getValidSessionCount());
            }
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        log.error("WebSocket异常", cause);
        ctx.close();
    }

    private void sendEnterSuccess(ChannelHandlerContext ctx) {
        try {
            String messageJson = "{\"type\":\"enterSuccess\"}";
            ctx.channel().writeAndFlush(new TextWebSocketFrame(messageJson));
        } catch (Exception e) {
            log.error("发送enterSuccess消息失败", e);
        }
    }
    
    private void sendRoomCoins(ChannelHandlerContext ctx, String roomId) {
        try {
            List<Map<String, Object>> coins = roomService.getRoomCoins(roomId);
            
            Map<String, Object> coinStartMessage = new HashMap<>();
            coinStartMessage.put("type", coinStart);
            coinStartMessage.put("data", coins);
            
            String messageJson = objectMapper.writeValueAsString(coinStartMessage);
            ctx.channel().writeAndFlush(new TextWebSocketFrame(messageJson));
            
            log.debug("向玩家发送房间 {} 的金币数据，共 {} 个金币", roomId, coins.size());
        } catch (Exception e) {
            log.error("发送房间金币数据失败", e);
        }
    }
    
    private void broadcastPlayerEnter(String roomId, Player newPlayer) {
        try {
            // 创建HeroData对象
            HeroData heroData = new HeroData();
            heroData.setHeroId(newPlayer.getHeroId());
            heroData.setPosition(newPlayer.getPosition());
            heroData.setRotation(newPlayer.getRotation());
            heroData.setAnimation(newPlayer.getAnimation());
            
            // 获取Hero对象
            Hero hero = heroMapper.selectById(newPlayer.getHeroId());
            if (hero != null && hero.getCharacterId() != null) {
                // 根据characterId查找character并封装成GameCharacterVo
                GameCharacter gameCharacter = gameCharacterMapper.selectById(hero.getCharacterId());
                if (gameCharacter != null) {
                    GameCharacterVo characterVo = new GameCharacterVo();
                    characterVo.setId(gameCharacter.getId());
                    characterVo.setModelUrl(gameCharacter.getModelUrl());
                    characterVo.setPreviewUrl(gameCharacter.getPreviewUrl());
                    characterVo.setName(gameCharacter.getName());
                    characterVo.setStyleTypeId(gameCharacter.getStyleTypeId());
                    hero.setCharacter(characterVo);
                }
            }
            heroData.setHero(hero);
            
            // 创建enter消息
            String messageJson = objectMapper.writeValueAsString(heroData);
            String enterMessage = "{\"type\":\"enter\"," + messageJson.substring(1);
            
            // 获取房间内所有玩家
            List<Player> roomPlayers = roomService.getPlayersInRoom(roomId);
            
            for (Player player : roomPlayers) {
                // 排除新加入的玩家自己
                if (newPlayer.getHeroId().equals(player.getHeroId())) {
                    continue;
                }
                
                // 通过clientId获取对应的channel
                String clientId = player.getSessionId();
                if (clientId != null) {
                    ChannelHandlerContext ctx = clients.get(clientId);
                    if (ctx != null && ctx.channel().isActive()) {
                        ctx.channel().writeAndFlush(new TextWebSocketFrame(enterMessage));
                    }
                }
            }
            
            log.info("向房间 {} 中的其他玩家广播了新玩家 {} 的enter消息", roomId, newPlayer.getUsername());
        } catch (Exception e) {
            log.error("广播玩家enter消息失败", e);
        }
    }
    
    private void broadcastPlayerMotion(String roomId, Integer playerId, Integer heroId, Position position, Rotation rotation, String animation) {
        try {
            // 创建motion消息
            String motionMessage = String.format(
                "{\"type\":\"motion\",\"heroId\":%d,\"position\":{\"x\":%f,\"y\":%f,\"z\":%f},\"rotation\":{\"y\":%f},\"animation\":\"%s\"}",
                heroId, position.getX(), position.getY(), position.getZ(), rotation.getY(), animation
            );
            System.out.println(motionMessage);
            // 获取房间内所有玩家
            List<Player> roomPlayers = roomService.getPlayersInRoom(roomId);
            
            for (Player player : roomPlayers) {
                // 排除移动的玩家自己
                if (heroId.equals(player.getHeroId())) {
                    continue;
                }
                
                // 通过clientId获取对应的channel
                String clientId = player.getSessionId();
                if (clientId != null) {
                    ChannelHandlerContext ctx = clients.get(clientId);
                    if (ctx != null && ctx.channel().isActive()) {
                        ctx.channel().writeAndFlush(new TextWebSocketFrame(motionMessage));
                    }
                }
            }
            
            log.debug("向房间 {} 中的其他玩家广播了玩家 {} 的移动消息", roomId, heroId);
        } catch (Exception e) {
            log.error("广播玩家移动消息失败", e);
        }
    }
    
    private void broadcastEmotionMessage(String roomId, Long playerId, Integer heroId, String image) {
        try {
            // 创建emotion消息
            String emotionMessage = String.format(
                "{\"type\":\"emotion\",\"heroId\":%d,\"image\":\"%s\"}",
                heroId, image
            );
            
            // 获取房间内所有玩家
            List<Player> roomPlayers = roomService.getPlayersInRoom(roomId);
            
            for (Player player : roomPlayers) {
                // 排除发送表情包的玩家自己
                if (heroId.equals(player.getHeroId())) {
                    continue;
                }
                
                // 通过clientId获取对应的channel
                String clientId = player.getSessionId();
                if (clientId != null) {
                    ChannelHandlerContext ctx = clients.get(clientId);
                    if (ctx != null && ctx.channel().isActive()) {
                        ctx.channel().writeAndFlush(new TextWebSocketFrame(emotionMessage));
                    }
                }
            }
            
            log.debug("向房间 {} 中的其他玩家广播了玩家 {} 的表情包消息", roomId, playerId);
        } catch (Exception e) {
            log.error("广播表情包消息失败", e);
        }
    }
    
    private void broadcastPlayerLeave(String roomId, Integer heroId) {
        try {
            // 创建leave消息
            String leaveMessage = String.format(
                "{\"type\":\"leave\",\"heroId\":%d}",
                heroId
            );
            
            // 获取房间内所有玩家
            List<Player> roomPlayers = roomService.getPlayersInRoom(roomId);
            
            for (Player player : roomPlayers) {
                // 通过clientId获取对应的channel
                String clientId = player.getSessionId();
                if (clientId != null) {
                    ChannelHandlerContext ctx = clients.get(clientId);
                    if (ctx != null && ctx.channel().isActive()) {
                        ctx.channel().writeAndFlush(new TextWebSocketFrame(leaveMessage));
                    }
                }
            }
            
            log.info("向房间 {} 中的其他玩家广播了玩家 {} 的离开消息", roomId, heroId);
        } catch (Exception e) {
            log.error("广播玩家离开消息失败", e);
        }
    }
}
